//************************//
//   Programma che:
//   1) calcola la minima distanza di Hamming tra le codeword, presenti nel file, di un codice composto da N codeword lunghe M bit 
//   2) genera e salva su un file un codice composto da codeword lunghe M bit che hanno tra loro una distanza di Hamming almeno pari a D
//************************//
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define MAX_CODEWORD_LENGTH 32
#define MAX_FUNCTION_NAME_LENGTH 100
#define MAX_ERROR_MESSAGE_LENGTH 250

// Elemento della lista che conterrà tutte le codeword del codice
typedef struct codeword_list_element
{
	char* codeword;
	struct codeword_list_element* next;
} codewordListElement;

// Elemento della lista che conterrà i messaggi di errore
typedef struct error_list_element
{
	char* errorMessage;
	char* functionName;
	time_t time;
	struct error_list_element* next;
} errorListElement;

// Prototipi delle funzioni
int HammingDistance_File(FILE*);
int HammingDistance_StrCode(char*, codewordListElement*, int);
int HammingDistance_StrStr(char*, char*, int);
int AddCodewordToListHead(char*, int, codewordListElement**);
int NextCodeword(char*, int);
int CreateCodeFromHammingDistance(FILE*, int, int);
int SaveCodeToFile(FILE*, codewordListElement*, int, int);
int FreeCodewordList(codewordListElement**);
void AddError(char* errorMessage, char* functionName);
void PrintErrors();
void FreeErrorList();

// Testa della lista degli errori (variabile globale)
errorListElement* errorListHead = NULL;

int main()
{
	FILE* fp = NULL;
	char buffer[500];

	int selectedCodewordLength = -1;
	int selectedHammingDistance = -1;
	int hammingDistance, codewordCount;

	char choice[80];

	// Testa della lista delle codeword
	codewordListElement* codewordListHead = NULL;

	// Menù di scelta della funzione da attivare
	do
	{
		printf("\n\n//*** DISTANZA DI HAMMING ***//");
		printf("\n1) Calcola la distanza di Hamming del file");
		printf("\n2) Genera un file con distanza di Hamming definita");
		printf("\n0) Esci");
		printf("\nInserisci la scelta: ");
		fgets(choice, sizeof(choice), stdin);

		switch (choice[0])
		{
		case '0':
			break;

		case '1':

			printf("\nInserisci il nome del file: ");

			if (fgets(buffer, sizeof(buffer), stdin) != NULL)
			{
				buffer[strlen(buffer) - 1] = '\0';
				fp = fopen(buffer, "r");

				if (fp != NULL)
				{
					hammingDistance = HammingDistance_File(fp);

					if (hammingDistance >= 0)
					{
						printf("\nLa distanza di Hamming del codice e': %d", hammingDistance);
					}
					else
					{
						AddError("Errore nel calcolo della distanza di Hamming", "main");
					}


					fclose(fp);
				}
				else
				{
					AddError("Errore nell'apertura del file delle codeword", "main");
				}
			}
			else
			{
				AddError("Errore nella lettura del nome del file delle codeword", "main");
			}

			break;

		case '2':

			printf("\nInserisci il nome del file: ");

			if (fgets(buffer, sizeof(buffer), stdin) != NULL)
			{
				buffer[strlen(buffer) - 1] = '\0';
				fp = fopen(buffer, "w");

				if (fp != NULL)
				{
					printf("Inserisci la lunghezza delle codeword: ");

					if (fgets(buffer, sizeof(buffer), stdin) != NULL && sscanf(buffer, "%d", &selectedCodewordLength) == 1 && selectedCodewordLength > 0 && selectedCodewordLength <= MAX_CODEWORD_LENGTH)
					{
						printf("Inserisci la distanza di Hamming: ");

						if (fgets(buffer, sizeof(buffer), stdin) != NULL && sscanf(buffer, "%d", &selectedHammingDistance) == 1 && selectedHammingDistance >= 0)
						{
							codewordCount = CreateCodeFromHammingDistance(fp, selectedCodewordLength, selectedHammingDistance);

							if (codewordCount >= 0)
							{
								printf("\nNumero di codeword valide: %d", codewordCount);
							}
							else
							{
								AddError("Errore nella generazione delle codeword", "main");
							}
						}
						else
						{
							AddError("Errore nella lettura della distanza di Hamming", "main");
						}
					}
					else
					{
						AddError("Errore nella lettura della lunghezza della codeword", "main");
					}

					fclose(fp);
				}
				else
				{
					AddError("Errore nell'apertura del file delle codeword", "main");
				}
			}
			else
			{
				AddError("Errore nella lettura del nome del file delle codeword", "main");
			}

			break;

		default:
			printf("\nValore non valido! Scegliere uno tra i seguenti valori: 0, 1, 2");
		}

		// Visualizzo gli errori
		PrintErrors();

		// Cancello la lista degli errori
		FreeErrorList();
	} while (choice[0] != '0');

	return 0;
}

// Calcola e ritorna la minima distanza di Hamming tra le codeword, presenti nel file, di un codice composto da N codeword lunghe M bit.
// Parametri:
// fp: file contenente le codeword (input)
// return value: la distanza di Hamming del codice (-1: errore)
int HammingDistance_File(FILE* fp)
{
	int codewordLength, codewordCount, fileHammingDistance = -1, hammingDistance, i;
	char buffer[500];
	codewordListElement* head = NULL;

	if (fp == NULL)
	{
		AddError("Puntatore al file delle codeword uguale a NULL", "HammingDistance_File");
		return -1; // Errore
	}

	// Leggo l'intestazione del file
	if (fgets(buffer, sizeof(buffer), fp) == NULL || sscanf(buffer, "%d%d", &codewordLength, &codewordCount) != 2)
	{
		AddError("Lettura dell'intestazione del file delle codeword fallita", "HammingDistance_File");
		return -1; // Errore
	}

	// Inizializzo la distanza di Hamming del file alla lunghezza della codeword
	fileHammingDistance = codewordLength;

	// Leggo le codeword
	for (i = 0; i < codewordCount; i++)
	{
		if (fgets(buffer, sizeof(buffer), fp) == NULL)
		{
			AddError("Lettura della codeword fallita", "HammingDistance_File");
			return -1; // Errore
		}

		if ((int)strlen(buffer) < codewordLength)
		{
			AddError("Numero di bit dell codeword letta errato", "HammingDistance_File");
			return -1; // Errore
		}

		// Calcolo la distanza di Hamming tra la codeword contenuta in buffer ed ogni codeword già contenuta nella lista.
		hammingDistance = HammingDistance_StrCode(buffer, head, codewordLength);

		if (hammingDistance == -1)
		{
			AddError("Errore nel calcolo della distanza di Hamming", "HammingDistance_File");
			return -1; // Errore
		}

		// Nuovo distanza di Hamming minima del file?
		if (hammingDistance < fileHammingDistance)
			fileHammingDistance = hammingDistance;

		// Inserisco la nuova codeword in testa alla lista
		if (AddCodewordToListHead(buffer, codewordLength, &head) == -1)
		{
			AddError("Errore nell'inserimento nella lista delle codeword", "HammingDistance_File");
			return -1; // Errore
		}
	}

	if (FreeCodewordList(&head) == -1)
	{
		AddError("Errore nel rilascio delle risorse della lista delle codeword", "HammingDistance_File");
		return -1; // Errore
	}

	return fileHammingDistance;
}

// Calcola e ritorna il valore della distanza di Hamming tra la codeword e quelle già presenti nella lista (codice)
// Parametri:
// codeword: la codeword da confrontare con quelle già presenti nel codice
// head: testa della lista contenente le codeword del codice
// codewordLength: la lunghezza delle codeword
// return value: la distanza di Hamming (-1: errore)
int HammingDistance_StrCode(char* codeword, codewordListElement* head, int codewordLength)
{
	codewordListElement* p;
	int hammingDistance, codeHammingDistance;

	if (codeword == NULL || codewordLength > MAX_CODEWORD_LENGTH || codewordLength <= 0)
	{
		AddError("Errore nei parametri passati alla funzione", "HammingDistance_StrCode");
		return -1; // Errore
	}

	// Inizializzo la distanza di Hamming del codice alla lunghezza della codeword
	codeHammingDistance = codewordLength;

	// Scorro la lista della codeword e calcolo la distanza di Hamming tra la codeword contenuta in buffer
	// ed ogni codeword già contenuta nella lista.
	p = head;
	while (p != NULL)
	{
		hammingDistance = HammingDistance_StrStr(codeword, p->codeword, codewordLength);

		if (hammingDistance == -1)
		{
			AddError("Errore nel calcolo della distanza di Hamming", "HammingDistance_StrCode");
			return -1; // Errore
		}

		// Nuovo distanza di Hamming minima del file?
		if (hammingDistance < codeHammingDistance)
			codeHammingDistance = hammingDistance;

		p = p->next;
	}

	return codeHammingDistance;
}

// Calcola e ritorna il valore della distanza di Hamming tra due codeword
// Parametri:
// codeword_1, codeword_2: le codeword tra cui calcolare la distanza di hamming
// codewordLength: la lunghezza delle codeword
// return value: la distanza di Hamming (-1: errore)
int HammingDistance_StrStr(char* codeword_1, char* codeword_2, int codewordLength)
{
	int hammingDistance = 0, i;

	if (codeword_1 == NULL || codeword_2 == NULL || codewordLength > MAX_CODEWORD_LENGTH || codewordLength <= 0)
	{
		AddError("Errore nei parametri passati alla funzione", "HammingDistance_StrStr");
		return -1; // Errore
	}

	for (i = 0; i < codewordLength; i++)
	{
		if (codeword_1[i] != codeword_2[i])
			hammingDistance++;
	}

	return hammingDistance;
}

// Genera e salva su un file un codice composto da codeword lunghe M bit che hanno tra loro una distanza di Hamming almeno pari a D. Ritorna il numero di codeword generate.
// Parametri:
// FILE*: file contenente le codeword (output)
// codewordLength: la lunghezza della codeword
// hammingDistance: distanza di Hamming desiderata
// return value: numero di codeword valide (-1: errore)
int CreateCodeFromHammingDistance(FILE* fp, int codewordLength, int hammingDistance)
{
	int codewordCount = 0; // Numero di codeword valide (che rispettano la distanza di Hamming richiesta)	
	codewordListElement* head = NULL;
	int returnValue = 0;
	char* codeword = NULL;

	if (fp == NULL || codewordLength > MAX_CODEWORD_LENGTH || codewordLength <= 0 || hammingDistance < 0)
	{
		AddError("Errore nei parametri passati alla funzione", "HammingDistance_StrStr");
		return -1; // Errore
	}

	codeword = (char*)malloc(codewordLength * sizeof(char));
	if (codeword == NULL)
	{
		AddError("Errore nell'allocazione della nuova codeword", "CreateCodeFromHammingDistance");
		return -1; // Errore
	}

	// Inizializzo la funzione "NextCodeword"
	if (NextCodeword(NULL, codewordLength) != 2)
	{
		AddError("Inizializzazione di \"NextCodeword\" fallita", "CreateCodeFromHammingDistance");
		return -1; // Errore
	}

	// Genero tutte le possibili codeword
	while ((returnValue = NextCodeword(codeword, codewordLength)) == 1)
	{
		returnValue = HammingDistance_StrCode(codeword, head, codewordLength);
		if (returnValue == -1)
		{
			AddError("Errore nel calcolo della distanza di Hamming", "CreateCodeFromHammingDistance");
			return -1; // Errore
		}

		if (returnValue >= hammingDistance)
		{
			// Nuova codeword valida
			if (AddCodewordToListHead(codeword, codewordLength, &head) == -1)
			{
				AddError("Errore nell'inserimento nella lista delle codeword", "CreateCodeFromHammingDistance");
				return -1; // Errore
			}

			codewordCount++;
		}
	}

	if (returnValue == -1)
	{
		AddError("Errore nella generazione della nuova codeword", "CreateCodeFromHammingDistance");
		return -1; // Errore
	}

	// Salvo le codeword sul file
	if (SaveCodeToFile(fp, head, codewordLength, codewordCount) == -1)
	{
		AddError("Errore nella scrittura delle codeword sul file", "CreateCodeFromHammingDistance");
		return -1; // Errore
	}

	if (FreeCodewordList(&head) == -1)
	{
		AddError("Errore nel rilascio delle risorse della lista delle codeword", "CreateCodeFromHammingDistance");
		return -1; // Errore
	}

	return codewordCount;
}

// Genera tutte le possibili codeword di N bit
// Ad ogni chiamata genera la codeword successiva e ne ritorna il puntatore
// La funzione deve essere inizializzata prima di essere utilizzata
// Parametri:
// codeword: la nuova codeword generata (se NULL inizializza la funzione)
// codewordLength: la lunghezza delle codeword (<=MAX_CODEWORD_LENGTH) - Parametro utilizzato solo se codeword = NULL
// return value: -1: errore; 0: nessuna ulteriore codeword generabile; 1: nuova codeword generata; 2: inizializzazione riuscita
int NextCodeword(char* codeword, int codewordLength)
{
	static int noMoreCodeword = 0; // 1 => no more codeword
	int i;
	static char* newCodeword;
	static int newCodewordLength = -1;

	if (codewordLength > MAX_CODEWORD_LENGTH || codewordLength <= 0)
	{
		AddError("Errore nei parametri passati alla funzione", "NextCodeword");
		return -1; // Errore
	}

	if (codeword == NULL)
	{
		if (newCodeword != NULL)
		{
			free(newCodeword);
		}

		newCodeword = (char*)malloc(codewordLength * sizeof(char));

		if (newCodeword == NULL)
		{
			AddError("Errore nell'allocazione della nuova codeword", "NextCodeword");
			return -1; // Errore
		}

		// Inizializzazione
		newCodewordLength = codewordLength;
		for (i = 0; i < newCodewordLength; i++)
			newCodeword[i] = '0';
		noMoreCodeword = 0;
		return 2; // Inizializzazione riuscita
	}

	if (noMoreCodeword == 1)
		return 0; // Nessuna nuova codeword generabile

	// Copio la nuova codeword
	strncpy(codeword, newCodeword, newCodewordLength);

	// Genero la codeword successiva (sommo 1 alla codeword precedente)
	for (i = 0; i < newCodewordLength && newCodeword[i] == '1'; i++)
		newCodeword[i] = '0';
	if (i < newCodewordLength)
		newCodeword[i] = '1';
	else
		noMoreCodeword = 1;

	return 1; // Nuova codeword generata
}

// Aggiunge una nuova codeword alla lista
// Parametri:
// codeword: la codeword da aggiungere alla lista
// codewordLength: la lunghezza della codeword
// p_head: puntatore alla testa della lista delle codeword
// return value: 0: ok; -1: errore
int AddCodewordToListHead(char* codeword, int codewordLength, codewordListElement** p_head)
{
	codewordListElement* p;

	if (codeword == NULL || codewordLength > MAX_CODEWORD_LENGTH || codewordLength <= 0 || p_head == NULL)
	{
		AddError("Errore nei parametri passati alla funzione", "AddCodewordToListHead");
		return -1; // Errore
	}

	// Inserisco la nuova codeword in testa alla lista
	p = (codewordListElement*)malloc(sizeof(codewordListElement));

	if (p == NULL)
	{
		AddError("Errore nell'allocazione del nuovo elemento della lista delle codeword", "AddCodewordToListHead");
		return -1; // Errore
	}

	p->codeword = (char*)malloc(codewordLength * sizeof(char));

	if (p->codeword == NULL)
	{
		AddError("Errore nell'allocazione della nuova codeword", "AddCodewordToListHead");
		return -1; // Errore
	}

	strncpy(p->codeword, codeword, codewordLength);

	p->next = *p_head;
	*p_head = p;

	return 0;
}

// Salva le codeword del codice (contenute nella lista) su file
// Paremetri:
// FILE*: file contenente le codeword (output)
// head: puntatore alla testa della lista contenente le codeword
// codewordLength: la lunghezza della codeword
// codewordCount: numero di codeword valide
// return value: numero di codeword valide (-1: errore)
int SaveCodeToFile(FILE* fp, codewordListElement* head, int codewordLength, int codewordCount)
{
	codewordListElement* p;
	int i;

	if (fp == NULL || codewordLength > MAX_CODEWORD_LENGTH || codewordLength <= 0 || codewordCount < 0)
	{
		AddError("Errore nei parametri passati alla funzione", "SaveCodeToFile");
		return -1; // Errore
	}

	// Scrivo l'intestazione del file
	if (fprintf(fp, "%d %d\n", codewordLength, codewordCount) < 0)
	{
		AddError("Errore nella scrittura dell'intestazione del file", "SaveCodeToFile");
		return -1; // Errore
	}

	// Scrivo le codeword
	for (p = head; p != NULL; p = p->next)
	{
		for (i = 0; i < codewordLength; i++)
		{
			if (fputc(p->codeword[i], fp) == EOF)
			{
				AddError("Errore nella scrittura della codeword sul file", "SaveCodeToFile");
				return -1; // Errore
			}
		}
		if (fputc('\n', fp) == EOF)
		{
			AddError("Errore nella scrittura della codeword sul file", "SaveCodeToFile");
			return -1; // Errore
		}
	}

	return codewordCount;
}

// Libera le risorse occupate dalla lista delle codeword
// Parametri:
// p_head: puntatore alla testa della lista delle codeword
// return value: 0: OK; -1: errore
int FreeCodewordList(codewordListElement** p_head)
{
	codewordListElement* p;

	if (p_head == NULL)
	{
		AddError("Errore nei parametri passati alla funzione", "freeCodewordList");
		return -1; // Errore
	}

	while (*p_head != NULL)
	{
		p = *p_head;
		*p_head = (*p_head)->next;

		free(p->codeword);
		free(p);
	}

	return 0;
}

// Aggiunge un elemento alla lista degli errori e ritorna un puntatore alla testa della lista
// Parametri:
// errorMessage: messaggio di errore da aggiungere alla lista
// functionName: nome della funzione che ha generato l'errore
// N.B.: se l'errore viene generato dalla funzione stessa, questo viene immediatamente visualizzato
void AddError(char* errorMessage, char* functionName)
{
	errorListElement* p = NULL;

	// Errore da inserire?
	if (errorMessage == NULL || functionName == NULL)
	{
		AddError("Errore nei parametri passati alla funzione", "AddError");
		return; // Errore
	}

	// Creo il nuovo elemento della lista
	p = (errorListElement*)malloc(sizeof(errorListElement));

	if (p == NULL)
	{
		printf("ERRORE: impossibile allocare il nuovo elemento della lista (AddError)"); // Errore
		return;
	}

	p->errorMessage = (char*)malloc(sizeof(char) * (MAX_ERROR_MESSAGE_LENGTH + 1));
	p->functionName = (char*)malloc(sizeof(char) * (MAX_FUNCTION_NAME_LENGTH + 1));

	if (p->errorMessage == NULL || p->functionName == NULL)
	{
		printf("ERRORE: impossibile allocare le stringhe (AddError)"); // Errore
		return;
	}

	// Copio i valori
	strncpy(p->errorMessage, errorMessage, MAX_ERROR_MESSAGE_LENGTH); // Copio al massimo "MAX_ERROR_MESSAGE_LENGTH" caratteri
	p->errorMessage[MAX_ERROR_MESSAGE_LENGTH] = '\0'; // Termina la stringa in caso abbia copiato "MAX_ERROR_MESSAGE_LENGTH" caratteri
	strncpy(p->functionName, functionName, MAX_FUNCTION_NAME_LENGTH);
	p->functionName[MAX_FUNCTION_NAME_LENGTH] = '\0';
	p->time = time(NULL);

	// Inserisco il nuovo elemento in testa alla lista
	p->next = errorListHead;
	errorListHead = p;
}

// Visualizza a video la lista degli errori
void PrintErrors()
{
	errorListElement* p = errorListHead;
	char* strTime;

	if (p != NULL)
	{
		printf("\n\n//********** ERRORI **********//\n");
	}

	while (p != NULL)
	{
		if (p->errorMessage != NULL && p->functionName != NULL)
		{
			strTime = ctime(&(p->time));
			strTime[strlen(strTime) - 1] = '\0'; // Elimina lo '\n' inserito automaticamente da ctime
			printf("%s: ", strTime);
			printf("%s ", p->errorMessage);
			printf("(%s)", p->functionName);
			printf("\n");
		}

		p = p->next;
	}

	if (errorListHead != NULL)
	{
		printf("//****************************//\n\n");
	}
}

// Libera le risorse occupate dalla lista degli errori
void FreeErrorList()
{
	errorListElement* p;

	while (errorListHead != NULL)
	{
		p = errorListHead;
		errorListHead = errorListHead->next;

		free(p->errorMessage);
		free(p->functionName);
		free(p);
	}
}